package sebastiankerckhof.scene.geometry;


import java.util.ArrayList;
import java.util.List;

import sebastiankerckhof.math.Matrix4f;
import sebastiankerckhof.math.Vector3f;
import sebastiankerckhof.render.raytrace.Ray;
import sebastiankerckhof.render.raytrace.Hit.Hit;
import sebastiankerckhof.scene.material.Material;
import sebastiankerckhof.scene.texture.Texture;
import sebastiankerckhof.scene.transformation.TransformationStack;

public class BVHNode implements Surface{

	private BBox boundingBox;
	private Surface left, right;

	public BVHNode(List<? extends Surface> surfaces, int axis) {
		int n = surfaces.size();
		if (n == 1) {
			left = surfaces.get(0);
			right = null;
			boundingBox = left.getBBox();
		} else if (n == 2) {
			left = surfaces.get(0);
			right = surfaces.get(1);
			boundingBox = left.getBBox().combine(right.getBBox());
		} else {
			ArrayList<Surface> leftSurfaces = new ArrayList<Surface>();
			ArrayList<Surface> rightSurfaces = new ArrayList<Surface>();
			try {
				split(surfaces, axis, leftSurfaces, rightSurfaces);
				left = new BVHNode(leftSurfaces, (axis + 1) % 3);
				right = new BVHNode(rightSurfaces, (axis + 1) % 3);
				boundingBox = left.getBBox().combine(
						right.getBBox());
			} catch (Exception e) {
				System.out.println(e.getMessage());
			}
		}
	}

	@Override
	public BBox getBBox() {
		return this.boundingBox;
	}

	// TODO Eventueel versnellen door lijst telkens te verdelen in 2.
	private float getMidPoint(List<? extends Surface> surfaces, int axis)
			throws Exception {
		BBox bbox = surfaces.get(0).getBBox();
		for (Surface prim : surfaces) {
			bbox = bbox.combine(prim.getBBox());
		}
		return (bbox.min.get(axis) / 2f + bbox.max
				.get(axis) / 2f);
	}

	@Override
	public boolean hit(Ray ray, float t0, Hit h) {
		boolean hit = false;
		
		if (this.boundingBox.hit(ray, t0, h.distance)) {
			
			hit = this.left.hit(ray, t0, h);
			if (right != null)
				hit = this.right.hit(ray, t0, h) || hit;
		}
		
		return hit;
	}

	private void split(List<? extends Surface> surfaces, int axis,
			List<Surface> smaller, List<Surface> larger)
			throws Exception {
		
		float midPoint = getMidPoint(surfaces, axis);

		for (Surface s : surfaces) {
			if (s.getMidPoint(axis) < midPoint) 
				smaller.add(s);
			 else 
				larger.add(s);
		}
		
		if (surfaces.size() == smaller.size()) {
			larger.add(smaller.remove(smaller.size()-1));
		} else if (surfaces.size() == larger.size()) {
			smaller.add(larger.remove(larger.size()-1));
		}
	}

	@Override
	public Texture getTexture() {return null;}

	@Override
  public void setMaterial(Material m) {}

	@Override
  public void setTexture(Texture t) {}
	
	@Override
	public Material getMaterial() {return null;}


	@Override
  public void transform(Matrix4f M, Matrix4f N) {
		left.transform(M, N);
		
		if(right != null)
			right.transform(M, N);
		
		updateBBox();
  }

	@Override
  public float getMidPoint(int axis) {
	  return boundingBox.getMidPoint(axis);
  }

	@Override
  public void setMotion(TransformationStack t) {
		left.setMotion(t);
		
		if(right != null)
			right.setMotion(t);//TODO update bbox!!
  }
	
	public void updateBBox(){
		boundingBox = left.getBBox();
		if(right != null)
			boundingBox = boundingBox.combine(right.getBBox());
	}

	@Override
  public void transformToStep(float step) {
	  left.transformToStep(step);
	  if(right != null)
	  	right.transformToStep(step);
	  
	  updateBBox();
  }

	@Override
  public void transformFromStep(float step) {
	  left.transformFromStep(step);
	  if(right != null)
	  	right.transformFromStep(step);
	  updateBBox();
  }
}
